#define NUM_OF_SPHERES 2
#define NUM_OF_TRIANGLES 2

// geometry indexes
#define SPHERE 0
#define TRIANGLE 1

struct CSCamera
{
	float3 position;
	float pad1;
	float3 direction;
	float pad2;
	float3 right;
	float pad3;
	float3 down;
	float pad4;
};

struct Ray
{
	float3 origin;
	float pad1;
	float3 direction;
	float pad2;
};

struct Sphere
{
	float FindIntersection(Ray r)
	{
		float3 rayOrigin = r.origin;
		float3 rayDirection = r.direction;

		float a = 1.0;
		float b = (2.0 * (rayOrigin.x - position.x) * rayDirection.x) +
					(2.0 * (rayOrigin.y - position.y) * rayDirection.y) +
					(2.0 * (rayOrigin.z - position.z) * rayDirection.z);

		float c = (pow(rayOrigin.x - position.x, 2.0)) + 
					(pow(rayOrigin.y - position.y, 2.0)) + 
					(pow(rayOrigin.z - position.z, 2.0)) - 
					(radius * radius);

		float discriminant = b * b - 4.0 * c;

		if (discriminant > 0.0)
		{
			float root1 = (((-b) - sqrt(discriminant)) / 2.0) - 0.00001;
			
			if (root1 > 0.0)
			{
				return root1;
			}
			else
			{
				float root2 = ((sqrt(discriminant) - b) / 2.0) - 0.00001;
				return root2;
			}
		}
		else
		{
			return -1.0;
		}
	}

	float3 GetNormalAt(float3 coord)
	{
		float3 normal = coord - position;
		normal = normalize(normal);
		return normal;
	}

	float4 colour;
	float gloss;
	float3 pad3;
	float3 position;
	float pad1;
	float radius;
	float3 pad2;
};

struct Triangle
{
	float FindIntersection(Ray ray)
	{
		float distance = dot(normal, pointA);
		float a = dot(ray.direction, normal);

		if (a == 0)
		{
			// ray == parralel to triangle
			return -1;
		}
		else
		{
			float b = dot(normal, float3(ray.origin + (normal * distance) * -1.0));
			float distanceToPlane = -1.0 * b / a;

			float Wx = float3(ray.direction * distanceToPlane).x + ray.origin.x;
			float Wy = float3(ray.direction * distanceToPlane).y + ray.origin.y;
			float Wz = float3(ray.direction * distanceToPlane).z + ray.origin.z;

			float3 W = float3(Wx, Wy, Wz);

			float3 CToA = float3(pointC - pointA);
			float3 WToA = float3(W - pointA);
			float3 crossProd1;
			crossProd1 = cross(CToA, WToA);
			float test1 = dot(crossProd1, normal);

			float3 BToC = float3(pointB - pointC);
			float3 WToC = float3(W - pointC);
			float3 crossProd2;
			crossProd2 = cross(BToC, WToC);
			float test2 = dot(crossProd2, normal);

			float3 AToB = float3(pointA - pointB);
			float3 WToB = float3(W - pointB);
			float3 crossProd3;
			crossProd3 = cross(AToB, WToB);
			float test3 = dot(crossProd3, normal);

			if (test1 >= 0 && test2 >= 0 && test3 >= 0)
			{
				return distanceToPlane;
			}
			else
			{
				return -1;
			}
		}
	}
	float3 GetNormalAt(float3 coord)
	{
		return normal;
	}
	
	float4 colour;
	float gloss;
	float3 pad5;
	float3 pointA;
	float pad1;
	float3 pointB;
	float pad2;
	float3 pointC;
	float pad3;
	float3 normal;
	float pad4;
};

struct Plane
{
	float FindIntersection(Ray ray)
	{
		float dotProd = dot(ray.direction, normal);
		if (dotProd == 0)
		{
			// ray = parrallel to plane
			return -1.0f;
		}

		float3 toAdd = -(normal * distance);
		float3 rhsDot = ray.origin + toAdd;

		float dotNormal = dot(normal, rhsDot);
		float result = (dotNormal / dotProd) * -1.0f;
		return result;
	}

	float3 GetNormalAt()
	{
		return normal;
	}

	float4 colour;
	float gloss;
	float3 pad3;
	float3 normal;
	float pad1;
	float distance;
	float3 pad2;
};

// lighting
struct LightGlobal
{
	float3 position;
	float pad1;
	float4 colour;
};

// objects struct
//struct CSObjects
//{
//	Sphere sphere;
//};

cbuffer cbData : register (b0)
{
	Sphere spheres[NUM_OF_SPHERES];
	Triangle triangles[NUM_OF_TRIANGLES];
	CSCamera Camera;
	LightGlobal Light;
};

// change sphere later for more objects
float4 CalculateColour(float3 intersectionPoint, float3 intersectionDir, int intersectedType, int intersectedIndex,
					   Sphere spheres[NUM_OF_SPHERES], Triangle triangles[NUM_OF_TRIANGLES], LightGlobal lightSource, 
					   float accuracy, float ambientLight)
{
	if (intersectedType == SPHERE)
	{
		float4 intersectedObjectColour = spheres[intersectedIndex].colour;
		float3 objectNormal = spheres[intersectedIndex].GetNormalAt(intersectionPoint);
		// add the rest of calculate colour function

		float4 finalColour = intersectedObjectColour * ambientLight;
		float3 lightDirection = lightSource.position - intersectionPoint;
		lightDirection = normalize(lightDirection);

		float angle = dot(objectNormal, lightDirection);
		if (angle > 0.0)
		{
			// testing if shadow
			// not used until more objects can be added
			bool inShadow = false;

			float3 lightVec = lightDirection;
			float disFromLight = length(lightDirection);

			float3 shadowRayDirection = lightDirection;
			Ray shadowRay;
			shadowRay.origin = intersectionPoint;
			shadowRay.direction = shadowRayDirection;

			// test is shadow ray intersected with other objects
			//float planeIntersection = plane.FindIntersection(shadowRay);
			//if (planeIntersection > accuracy)
			//{
			//	if (planeIntersection <= disFromLight)
			//	{
			//		inShadow = true;
			//	}
			//}
			
			//testing shadow ray colllision with traigles
			float minTriIntersection = 10000.0; // culled distance
			int minTriIntersectionIndex = -1;
			float triIntersection = 0.0;
			for (int trisIndex = 0; trisIndex != NUM_OF_TRIANGLES; trisIndex++)
			{
				triIntersection = triangles[trisIndex].FindIntersection(shadowRay);
				if (triIntersection < minTriIntersection && triIntersection >= 0)
				{
					minTriIntersection = triIntersection;
					minTriIntersectionIndex = trisIndex;
				}
			}
			
			//testing shadow ray colllision with spheres
			float minSphereIntersection = 10000.0; // culled distance
			int minSphereIntersectionIndex = -1;
			float sphereIntersection = 0.0;
			for (int spheresIndex = 0; spheresIndex != NUM_OF_SPHERES; spheresIndex++)
			{
				sphereIntersection = spheres[spheresIndex].FindIntersection(shadowRay);
				if (sphereIntersection < minSphereIntersection && sphereIntersection >= 0)
				{
					minSphereIntersection = sphereIntersection;
					minSphereIntersectionIndex = spheresIndex;
				}
			}

			// if min sphere intersection is smaller than min triangle itnersection, the sphere is closer to the first collided object
			// with the camera
			if (minSphereIntersection < minTriIntersection)
			{
				// test if the min sphere intersection
				if (minSphereIntersection > accuracy && minSphereIntersection <= disFromLight)
				{
					inShadow = true;
				}

				// for spheres
				if (!inShadow)
				{
					// for each object test if the ray intersects it
					// for each intersected points, if it's greater than the accuracy 
					// and is smaller than the distance from the light, then pixel is in shadow, break;

					// if is not in shadow then calculate final colour

					finalColour += intersectedObjectColour * (lightSource.colour * angle);
					float objectGloss = spheres[intersectedIndex].gloss;
					if (objectGloss > 0.0 && objectGloss < 1.0)
					{
						float dotProd = dot(objectNormal, intersectionDir);
						float3 scalar1 = objectNormal * dotProd;
						float3 final1 = scalar1 + intersectionDir;

						float3 scalar2 = final1 * 2.0;
						float3 final2 = scalar2 + intersectionDir;

						float3 reflectionDirection = normalize(final2);

						float specular = dot(reflectionDirection, intersectionDir);
						if (specular > 0.0)
						{
							specular = pow(specular, 800.0);
							finalColour += lightSource.colour * specular * objectGloss;
						}
					}
				}
			}
			else
			{
				// test if the min triangle intersection
				if (minTriIntersection > accuracy && minTriIntersection <= disFromLight)
				{
					inShadow = true;
				}
				// for triangles
				if (!inShadow)
				{
					finalColour += intersectedObjectColour * (lightSource.colour * angle);
					float objectGloss = spheres[intersectedIndex].gloss;
					if (objectGloss > 0.0 && objectGloss < 1.0)
					{
						float dotProd = dot(objectNormal, intersectionDir);
						float3 scalar1 = objectNormal * dotProd;
						float3 final1 = scalar1 + intersectionDir;

						float3 scalar2 = final1 * 2.0;
						float3 final2 = scalar2 + intersectionDir;

						float3 reflectionDirection = normalize(final2);

						float specular = dot(reflectionDirection, intersectionDir);
						if (specular > 0.0)
						{
							specular = pow(specular, 800.0);
							finalColour += lightSource.colour * specular * objectGloss;
						}
					}
				}
			}			
		}

		return finalColour;
	}
	else
	{
		float4 intersectedObjectColour = triangles[intersectedIndex].colour;
		float3 objectNormal = triangles[intersectedIndex].GetNormalAt(intersectionPoint);
		// add the rest of calculate colour function

		float4 finalColour = intersectedObjectColour * ambientLight;
		float3 lightDirection = lightSource.position - intersectionPoint;
		lightDirection = normalize(lightDirection);

		float angle = dot(objectNormal, lightDirection);
		if (angle > 0.0)
		{
			// testing if shadow
			// not used until more objects can be added
			bool inShadow = false;

			float3 lightVec = lightDirection;
			float disFromLight = length(lightDirection);

			float3 shadowRayDirection = lightDirection;
			Ray shadowRay;
			shadowRay.origin = intersectionPoint;
			shadowRay.direction = shadowRayDirection;
			
			//testing shadow ray colllision with traigles
			float minTriIntersection = 10000.0; // culled distance
			int minTriIntersectionIndex = -1;
			float triIntersection = 0.0;
			for (int trisIndex = 0; trisIndex != NUM_OF_TRIANGLES; trisIndex++)
			{
				triIntersection = triangles[trisIndex].FindIntersection(shadowRay);
				if (triIntersection < minTriIntersection && triIntersection >= 0)
				{
					minTriIntersection = triIntersection;
					minTriIntersectionIndex = trisIndex;
				}
			}
			
			//testing shadow ray colllision with spheres
			float minSphereIntersection = 10000.0; // culled distance
			int minSphereIntersectionIndex = -1;
			float sphereIntersection = 0.0;
			for (int spheresIndex = 0; spheresIndex != NUM_OF_SPHERES; spheresIndex++)
			{
				sphereIntersection = spheres[spheresIndex].FindIntersection(shadowRay);
				if (sphereIntersection < minSphereIntersection && sphereIntersection >= 0)
				{
					minSphereIntersection = sphereIntersection;
					minSphereIntersectionIndex = spheresIndex;
				}
			}

			
			// if min sphere intersection is smaller than min triangle itnersection, the sphere is closer to the first collided object
			// with the camera
			if (minSphereIntersection < minTriIntersection)
			{
				// test if the min sphere intersection
				if (minSphereIntersection > accuracy && minSphereIntersection <= disFromLight)
				{
					inShadow = true;
				}

				// for spheres
				if (!inShadow)
				{
					// for each object test if the ray intersects it
					// for each intersected points, if it's greater than the accuracy 
					// and is smaller than the distance from the light, then pixel is in shadow, break;

					// if is not in shadow then calculate final colour

					finalColour += intersectedObjectColour * (lightSource.colour * angle);
					float objectGloss = spheres[intersectedIndex].gloss;
					if (objectGloss > 0.0 && objectGloss < 1.0)
					{
						float dotProd = dot(objectNormal, intersectionDir);
						float3 scalar1 = objectNormal * dotProd;
						float3 final1 = scalar1 + intersectionDir;

						float3 scalar2 = final1 * 2.0;
						float3 final2 = scalar2 + intersectionDir;

						float3 reflectionDirection = normalize(final2);

						float specular = dot(reflectionDirection, intersectionDir);
						if (specular > 0.0)
						{
							specular = pow(specular, 800.0);
							finalColour += lightSource.colour * specular * objectGloss;
						}
					}
				}
			}
			else
			{
				// test if the min triangle intersection
				if (minTriIntersection > accuracy && minTriIntersection <= disFromLight)
				{
					inShadow = true;
				}
				// for triangles
				if (!inShadow)
				{
					finalColour += intersectedObjectColour * (lightSource.colour * angle);
					float objectGloss = spheres[intersectedIndex].gloss;
					if (objectGloss > 0.0 && objectGloss < 1.0)
					{
						float dotProd = dot(objectNormal, intersectionDir);
						float3 scalar1 = objectNormal * dotProd;
						float3 final1 = scalar1 + intersectionDir;

						float3 scalar2 = final1 * 2.0;
						float3 final2 = scalar2 + intersectionDir;

						float3 reflectionDirection = normalize(final2);

						float specular = dot(reflectionDirection, intersectionDir);
						if (specular > 0.0)
						{
							specular = pow(specular, 800.0);
							finalColour += lightSource.colour * specular * objectGloss;
						}
					}
				}
			}			
		}


		return finalColour;
	}
}

//// change sphere later for more objects
//float4 CalculateColourPlane(float3 intersectionPoint, float3 intersectionDir, Triangle sphere, Plane plane, LightGlobal lightSource,
//					   float accuracy, float ambientLight)
//{
//	float4 intersectedObjectColour = plane.colour;
//	float3 objectNormal = plane.GetNormalAt();
//	// add the rest of calculate colour function
//
//	float4 finalColour = intersectedObjectColour * ambientLight;
//	float3 lightDirection = lightSource.position - intersectionPoint;
//	lightDirection = normalize(lightDirection);
//
//	float angle = dot(objectNormal, lightDirection);
//	if (angle > 0.0)
//	{
//		// testing if shadow
//		// not used until more objects can be added
//		bool inShadow = false;
//		
//		float3 lightVec = lightDirection;
//		float disFromLight = length(lightDirection);
//
//		float3 shadowRayDirection = lightDirection;
//		Ray shadowRay;
//		shadowRay.origin = intersectionPoint;
//		shadowRay.direction = shadowRayDirection;
//
//		// test is shadow ray intersected with sphere
//		float planeIntersection = sphere.FindIntersection(shadowRay);
//		if (planeIntersection > accuracy)
//		{
//			if (planeIntersection <= disFromLight)
//			{
//				inShadow = true;
//			}
//		}
//
//		if (!inShadow)
//		{
//			// for each object test if the ray intersects it
//			// for each intersected points, if it's greater than the accuracy 
//			// and is smaller than the distance from the light, then pixel is in shadow, break;
//
//			// if is not in shadow then calculate final colour
//
//			finalColour += plane.colour * (lightSource.colour * angle);
//
//			if (plane.gloss > 0.0 && plane.gloss < 1.0)
//			{
//				float dotProd = dot(objectNormal, intersectionDir);
//				float3 scalar1 = objectNormal * dotProd;
//				float3 final1 = scalar1 + intersectionDir;
//
//				float3 scalar2 = final1 * 2.0;
//				float3 final2 = scalar2 + intersectionDir;
//
//				float3 reflectionDirection = normalize(final2);
//
//				float specular = dot(reflectionDirection, intersectionDir);
//				if (specular > 0.0)
//				{
//					specular = pow(specular, 800.0);
//					finalColour += lightSource.colour * specular * sphere.gloss;
//				}
//			}
//		}
//	}
//
//
//	return finalColour;
//}

// debug buffer
struct DebugBuffer
{
	Triangle triangles[2];
};

Texture2D<float4> input : register(t0);
RWTexture2D<float4> result : register(u0);
RWStructuredBuffer<DebugBuffer> debugOut: register (u1);

#define NUM_OF_THREADS_X 1
#define NUM_OF_THREADS_Y 1

[numthreads(NUM_OF_THREADS_X, NUM_OF_THREADS_Y, 1)]
//[numthreads(32, 32, 1)]

void main(uint3 dispatchID : SV_DispatchThreadID, uint3 groupThreadID : SV_GroupThreadID)
{	
	//debugOut[dispatchID.x].triangles = triangles;

	float2 screenSize = float2(640, 480);
	float aspectRatio = 1.333333333333333;
	float2 amounts;
	amounts.x = ((dispatchID.x + 0.5) / screenSize.x) * aspectRatio - ((screenSize.x - screenSize.y) / screenSize.y / 2.0);
	amounts.y = ((screenSize.y - dispatchID.y) + 0.5) / screenSize.y;

	// ray calcs here
	float3 rayOrigin = Camera.position;
	float3 rayDirection = Camera.direction;
	rayDirection += (Camera.right * (amounts.x - 0.5)) + (Camera.down * (amounts.y - 0.5));
	rayDirection = normalize(rayDirection);
	//rayDirection *= -1.0;

	Ray ray;
	ray.origin = rayOrigin;
	ray.direction = rayDirection;
		
// testing object collisions with camera ray start
	//testing shadow ray colllision with traigles
	float minTriIntersection = 10000.0; // culled distance
	int minTriIntersectionIndex = -1;
	float triIntersection = 0.0;
	for (int trisIndex = 0; trisIndex != NUM_OF_TRIANGLES; trisIndex++)
	{
		triIntersection = triangles[trisIndex].FindIntersection(ray);
		if (triIntersection < minTriIntersection && triIntersection >= 0)
		{
			minTriIntersection = triIntersection;
			minTriIntersectionIndex = trisIndex;
		}
	}

	//testing shadow ray colllision with spheres
	float minSphereIntersection = 10000.0; // culled distance
	int minSphereIntersectionIndex = -1;
	float sphereIntersection = 0.0;
	for (int spheresIndex = 0; spheresIndex != NUM_OF_SPHERES; spheresIndex++)
	{
		sphereIntersection = spheres[spheresIndex].FindIntersection(ray);
		if (sphereIntersection < minSphereIntersection && sphereIntersection >= 0)
		{
			minSphereIntersection = sphereIntersection;
			minSphereIntersectionIndex = spheresIndex;
		}
	}	
// testing object collisions with camera ray end

	float4 finalColour = float4(0.0, 0.0, 0.0, 1.0);
	float accuracy = 0.00001;
	float ambientLight = 0.2;
	int intersectedType = -1;

	if (minSphereIntersection < minTriIntersection)
	{
		// closest object = a sphere
		intersectedType = SPHERE;

		float3 intersectionPoint = ray.origin + (ray.direction * minSphereIntersection);
		float3 intersectionDirection = ray.direction;

		finalColour = CalculateColour(intersectionPoint, intersectionDirection, intersectedType, minSphereIntersectionIndex,
										spheres, triangles, Light, accuracy, ambientLight);
	}
	else
	{
		// closest object = a triangle
		intersectedType = TRIANGLE;

		float3 intersectionPoint = ray.origin + (ray.direction * minTriIntersection);
		float3 intersectionDirection = ray.direction;

		finalColour = CalculateColour(intersectionPoint, intersectionDirection, intersectedType, minTriIntersectionIndex,
										spheres, triangles, Light, accuracy, ambientLight);
	}









	// find which object has shortest intersection
	// if sphere intersection is smaller than plane intersection
	//if (intersectionSphere < intersectionPlane && intersectionSphere != -1)
	//{
		//debugOut[dispatchID.x].called = 123;
		// intersected sphere?
//		if (intersectionSphere > accuracy)
//		{
//			float3 intersectionPoint = ray.origin + (ray.direction * intersectionSphere);
//			float3 intersectionDirection = ray.direction;
//			
//float4 CalculateColour(float3 intersectionPoint, float3 intersectionDir, int intersectedType, int intersectedIndex,
//					   Sphere spheres[NUM_OF_SPHERES], Triangle triangles[NUM_OF_TRIANGLES], LightGlobal lightSource, 
//					   float accuracy, float ambientLight)
//			// need to add extra objects
//			finalColour = CalculateColour(intersectionPoint, intersectionDirection, intersectedType, 
//										tri, plane, Light, accuracy, ambientLight);
//		}
	//}
	//else if (intersectionPlane != -1)
	//{
	//	// intesectied plane?
	//	if (intersectionPlane > accuracy)
	//	{
	//		float3 intersectionPoint = ray.origin + (ray.direction * intersectionPlane);
	//		float3 intersectionDirection = ray.direction;

	//		// need to add extra objects
	//		finalColour = CalculateColourPlane(intersectionPoint, intersectionDirection, tri, plane, Light, accuracy, ambientLight);
	//	}
	//}

	result[dispatchID.xy] = finalColour;	
}